/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections;
using System.Windows.Forms;
using Vj.MathLibrary;

namespace Vj.GA
{
    public enum SelectionOperators
    {
        Roulette, Random, Rank, Tournament
    }

    public enum CrossoverOperators
    {
        SinglePoint, DoublePoint, Uniform
    }

    public enum MutationOperators
    {
        FlipBit, SwapBits // Boundary, Uniform, Gaussian  (only for real valued chroms)
    }

    public enum ReplacementOperators
    {
        ChildReplaceParent, ChildReplaceWorse, ChildReplaceBest, ChildReplaceRandom
    }

    /// <summary>
    /// Summary description for Population.
    /// </summary>
    public class Population
    {
        private ArrayList myPop;
        private int alleleLength;
        private int numOfAlleles;

        private SelectionOperators selectionOperator;
        private MutationOperators mutationOperator;
        private CrossoverOperators crossoverOperator;
        private ReplacementOperators replacementOperator;
        private bool doElitism;

        private double worstFitness;

        public double WorstFitness
        {
            get { return worstFitness; }
        }

        private double bestFitness;

        public double BestFitness
        {
            get { return bestFitness; }
        }

        private double avgFitness;

        public double AvgFitness
        {
            get { return avgFitness; }
            set { avgFitness = value; }
        }

        public int PopSize
        {
            get { return myPop.Count; }
        }

        public int AlleleLength
        {
            get { return alleleLength; }
            set { alleleLength = value; }
        }

        public int Dimension
        {
            get { return numOfAlleles; }
        }

        private bool maximizeObjective = true;

        public bool MaximizeObjective
        {
            get { return maximizeObjective; }
            set { maximizeObjective = value; }
        }

        public Population(int popSize, int alleleLength, int numOfAlleles)
        {
            this.alleleLength = alleleLength;
            myPop = new ArrayList();
            Genome.ResetID();

            this.numOfAlleles = numOfAlleles;

            for (int i = 0; i < popSize; i++)
                myPop.Add(new Genome(numOfAlleles, alleleLength));
        }

        public Population(Genome[] genomeList)
        {
            this.alleleLength = genomeList[0].Length/genomeList[0].NumOfAlleles;
            myPop = new ArrayList();
            //Genome.ResetID(); will already have id

            this.numOfAlleles = genomeList[0].NumOfAlleles;

            for (int i = 0; i < genomeList.Length; i++)
                myPop.Add(genomeList[i]);
        }

        public void SetOperators(SelectionOperators selOp, MutationOperators muOp, CrossoverOperators coOp,
            ReplacementOperators replOp, bool elitism)
        {
            selectionOperator = selOp;
            mutationOperator = muOp;
            crossoverOperator = coOp;
            replacementOperator = replOp;
            doElitism = elitism;
        }

        public void DoGeneration(float muRate, float selRate)
        {
            ArrayList newPop = new ArrayList();

            int selCount = (int)(selRate * myPop.Count / 2);

            for (int i = 0; i < selCount; i++)
            {
                Genome parent1 = null;
                Genome parent2 = null;

                if (selectionOperator == SelectionOperators.Tournament)
                {
                    parent1 = TournamentSelection();
                    parent2 = TournamentSelection();
                }
                else if (selectionOperator == SelectionOperators.Random)
                {
                    parent1 = RandomSelection();
                    parent2 = RandomSelection();
                }
                else if (selectionOperator == SelectionOperators.Rank)
                {
                    parent1 = RankSelection();
                    parent2 = RankSelection();
                }
                else if (selectionOperator == SelectionOperators.Roulette)
                {
                    parent1 = RouletteSelection();
                    parent2 = RouletteSelection();
                }
                else
                {
                    throw new Exception("Invalid Selection Method");
                }

                Genome child1 = new Genome(numOfAlleles, alleleLength);
                Genome child2 = new Genome(numOfAlleles, alleleLength);

                if (crossoverOperator == CrossoverOperators.SinglePoint)
                    SinglePtCrossover(parent1, parent2, ref child1, ref child2);
                else if (crossoverOperator == CrossoverOperators.DoublePoint)
                    DoublePtCrossover(parent1, parent2, ref child1, ref child2);
                else if (crossoverOperator == CrossoverOperators.Uniform)
                    UniformCrossover(parent1, parent2, ref child1, ref child2);
                else
                    throw new Exception("Invalid Crossover Operator");

                if (mutationOperator == MutationOperators.FlipBit)
                {
                    child1.FlipBitMutate(muRate);
                    child2.FlipBitMutate(muRate);
                }
                else if (mutationOperator == MutationOperators.SwapBits)
                {
                    child1.SwapBitsMutate(muRate);
                    child2.SwapBitsMutate(muRate);
                }
                else
                    throw new Exception("Invalid Mutation Operator");

                newPop.Add(child1);
                newPop.Add(child2);
            }


            // replacement
            if (replacementOperator == ReplacementOperators.ChildReplaceBest)
            {
                for (int i = 0; i < newPop.Count; i++)
                    myPop[i] = newPop[i];
            }
            else if (replacementOperator == ReplacementOperators.ChildReplaceParent)
            {
                //myPop.RemoveRange(myPop.Count - newPop.Count, newPop.Count);
                //myPop.AddRange(newPop);
                MessageBox.Show("Current replacement operator not yet implemented!");
            }
            else if (replacementOperator == ReplacementOperators.ChildReplaceWorse)
            {
                //myPop.RemoveRange(myPop.Count - newPop.Count, newPop.Count);
                //myPop.AddRange(newPop);
                for (int i = 0; i < newPop.Count; i++)
                    myPop[myPop.Count-(i+1)] = newPop[i];

            }
            else if (replacementOperator == ReplacementOperators.ChildReplaceRandom)
            {
                for (int i = 0; i < newPop.Count; i++)
                {
                    int index = RandomGrabber.Next(newPop.Count - 1);
                    myPop[index] = newPop[i];
                }
            }
            else
                throw new Exception("Invalid Replacement Operator");


            //myPop.RemoveRange(myPop.Count - newPop.Count, newPop.Count);
            //myPop.AddRange(newPop);


        }

        private Genome RandomSelection()
        {
            int index = RandomGrabber.Next(myPop.Count);
            return (Genome) myPop[index];
        }

        private Genome RankSelection()
        {
            int index = RandomGrabber.Next(myPop.Count);
            return (Genome)myPop[index];
        }

        private Genome RouletteSelection()
        {
            int index = RandomGrabber.Next(myPop.Count);
            return (Genome)myPop[index];
        }


        private Genome Selection()
        {
            int index1 = RandomGrabber.Next(myPop.Count);
            int index2 = RandomGrabber.Next(myPop.Count);

            Genome chrom1 = (Genome)myPop[index1];
            Genome chrom2 = (Genome)myPop[index2];

            // comparison operator
            if (maximizeObjective && chrom1.Fitness > chrom2.Fitness)
                return chrom1;
            else if (!maximizeObjective && chrom1.Fitness < chrom2.Fitness)
                return chrom1;
            else
                return chrom2;
        }

        private Genome TournamentSelection()
        {
            int k = 10;
            Genome[] fitnessGenomeList = new Genome[k];

            for(int i = 0; i < k; i++)
            {
                int index = RandomGrabber.Next(myPop.Count);
                fitnessGenomeList[i] = (Genome)myPop[index];
            }

            Genome bestGenome = fitnessGenomeList[0];

            for (int i = 0; i < k-1; i++)
            {
                if (maximizeObjective && fitnessGenomeList[i].ScaledFitness > fitnessGenomeList[i + 1].ScaledFitness)
                    bestGenome = fitnessGenomeList[i];
                else if(!maximizeObjective && fitnessGenomeList[i].ScaledFitness < fitnessGenomeList[i + 1].ScaledFitness)
                    bestGenome = fitnessGenomeList[i];
            }

            return bestGenome;
        }

        public void Evaluate(FitFunc fitnessFunction)
        {
            double squaredSumFitness = 0.0;
            double sumFitness = 0.0;
            double sumScaledFitness = 0.0;

            for (int i = 0; i < myPop.Count; i++)
            {
                Genome chrom = (Genome)myPop[i];
                chrom.Fitness = fitnessFunction(chrom.AlleleValuesD);
                // for normalization
                sumFitness += chrom.Fitness;
                squaredSumFitness += chrom.Fitness*chrom.Fitness;
            }

            if (MaximizeObjective)
                myPop.Sort(new MaxChromComparer());
            else
                myPop.Sort(new MinChromComparer());

            Genome worst = (Genome)myPop[myPop.Count - 1];
            this.worstFitness = worst.Fitness;

            Genome best = (Genome)myPop[0];
            this.bestFitness = best.Fitness;

            this.avgFitness = sumFitness / myPop.Count;

            for (int i = 0; i < myPop.Count; i++)
            {
                Genome chrom = (Genome)myPop[i];
                chrom.ScaledFitness = chrom.Fitness/Math.Sqrt(squaredSumFitness);
                sumScaledFitness += chrom.ScaledFitness;
                chrom.AccumulatedFitness = sumScaledFitness;
            }
        }

        public void CalculateStats()
        {




        }


        public void SinglePtCrossover(Genome parent1, Genome parent2,
            ref Genome child1, ref Genome child2)
        {
            int index = RandomGrabber.Next(parent1.Length);

            for (int i = 0; i < index; i++)
            {
                child1[i] = parent1[i];
                child2[i] = parent2[i];
            }
            for (int i = index; i < parent1.Length; i++)
            {
                child1[i] = parent2[i];
                child2[i] = parent1[i];
            }
        }

        public void DoublePtCrossover(Genome parent1, Genome parent2,
            ref Genome child1, ref Genome child2)
        {
            int index1 = RandomGrabber.Next(parent1.Length);
            int index2 = RandomGrabber.Next(parent1.Length);

            while (index1 == index2)
                index2 = RandomGrabber.Next(parent1.Length);


            for (int i = 0; i < index1; i++)
            {
                child1[i] = parent1[i];
                child2[i] = parent2[i];
            }
            for (int i = index1; i < index2; i++)
            {
                child1[i] = parent2[i];
                child2[i] = parent1[i];
            }
            for (int i = index2; i < parent1.Length; i++)
            {
                child1[i] = parent2[i];
                child2[i] = parent1[i];
            }
        }

        public void UniformCrossover(Genome parent1, Genome parent2,
            ref Genome child1, ref Genome child2)
        {
            for (int i = 0; i < parent1.Length; i++)
            {
                if (RandomGrabber.NextBoolean())
                {
                    child1[i] = parent1[i];
                    child2[i] = parent2[i];
                }
                else
                {
                    child1[i] = parent2[i];
                    child2[i] = parent1[i];
                }
            }
        }

        public Genome GetGenome(int i)
        {
            return (Genome)myPop[i];
        }

        public override string ToString()
        {
            string temp = "Pop Size: " + myPop.Count + "\n";

            temp += "Best: " + bestFitness.ToString("N3") + "\r\n";
            temp += "Avg: " + avgFitness.ToString("N3") + "\r\n";
            temp += "Worst: " + worstFitness.ToString("N3") + "\r\n";

            temp += "--------------------------------------------\r\n";

            for (int i = 0; i < myPop.Count; i++)
                temp += "(" + i + ") " + myPop[i].ToString() + "\r\n \r\n";

            return temp;
        }

    }
}
